package clock.socoolby.com.clock.widget.animatorview.animator;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

import clock.socoolby.com.clock.widget.animatorview.AbstractAnimator;
import clock.socoolby.com.clock.widget.animatorview.I_AnimatorEntry;

//引用自https://www.zhangman523.cn/18.html

public class SnakeAnimator extends AbstractAnimator<SnakeAnimator.Snake> {
    private static final String TAG =SnakeAnimator.class.getName();

    public static final String NAME = "Snake";

    public SnakeAnimator() {
        super(1,10);
    }

    @Override
    public Snake createNewEntry() {
        randomColorIfAble();
        return new Snake(color,randomColor());
    }

    /**
     * Created by zhangman on 2018/1/8 14:43
     * Email: zhangman523@126.com
     */
    public  class Snake  implements I_AnimatorEntry {

        private List<List<GridSquare>> mGridSquare = new ArrayList<>();
        private List<GridPosition> mSnakePositions = new ArrayList<>();

        private GridPosition mSnakeHeader;//蛇头部位置
        private GridPosition mFoodPosition;//食物的位置
        private int mSnakeLength = 3;
        private long mSpeed = 8;
        private int mSnakeDirection = GameType.RIGHT;
        private boolean mIsEndGame = false;
        private int mGridSize = 20;
        private Paint mGridPaint = new Paint();
        private Paint mStrokePaint = new Paint();
        private int mRectSize = Util.dipToPX(context, 15);
        private int mStartX, mStartY;

        private boolean gridDrawAble=false;

        private int snakeColor,foodColor;

        public Snake(int snakeColor,int foodColor) {
            init();
            this.snakeColor=snakeColor;
            this.foodColor=foodColor;
        }

        private void init() {
            mStartX = width/ 2 - mGridSize * mRectSize / 2;
            mStartY = Util.dipToPX(context, 40);
            List<GridSquare> squares;
            for (int i = 0; i < mGridSize; i++) {
                squares = new ArrayList<>();
                for (int j = 0; j < mGridSize; j++) {
                    squares.add(new GridSquare(GameType.GRID));
                }
                mGridSquare.add(squares);
            }
            mSnakeHeader = new GridPosition(10, 10);
            mSnakePositions.add(new GridPosition(mSnakeHeader.getX(), mSnakeHeader.getY()));
            mFoodPosition = new GridPosition(0, 0);
            mIsEndGame = true;

            mGridPaint.reset();
            mGridPaint.setStyle(Paint.Style.FILL);
            mGridPaint.setAntiAlias(true);

            mStrokePaint.reset();
            mStrokePaint.setColor(snakeColor);
            mStrokePaint.setStyle(Paint.Style.STROKE);
            mStrokePaint.setAntiAlias(true);
        }


        private void refreshFood(GridPosition foodPosition) {
            mGridSquare.get(foodPosition.getX()).get(foodPosition.getY()).setType(GameType.FOOD);
        }

        public void setSpeed(long speed) {
            mSpeed = speed;
        }

        public void setGridSize(int gridSize) {
            mGridSize = gridSize;
        }

        public void setSnakeDirection(int snakeDirection) {
            if (mSnakeDirection == GameType.RIGHT && snakeDirection == GameType.LEFT) return;
            if (mSnakeDirection == GameType.LEFT && snakeDirection == GameType.RIGHT) return;
            if (mSnakeDirection == GameType.TOP && snakeDirection == GameType.BOTTOM) return;
            if (mSnakeDirection == GameType.BOTTOM && snakeDirection == GameType.TOP) return;
            mSnakeDirection = snakeDirection;
        }


        @Override
        public void move(int maxWidth, int maxHight) {
            if (!mIsEndGame) {
                robotRun();
                moveSnake(mSnakeDirection);
                if(eatFoodCheck()){

                }else if(mSnakePositions.size()>mSnakeLength) {
                    //GridPosition position = mSnakePositions.get(0);
                    //mGridSquare.get(position.getX()).get(position.getY()).setType(GameType.GRID);
                    mSnakePositions.remove(0);
                }
                checkCollision();
                //refreshGridSquare();
                //handleSnakeTail();
            }else
                reStartGame();
        }

        int goDirection;
        private void robotRun(){

            //预先碰撞判断,带扰动（）
            GridPosition headerPosition = mSnakeHeader;
            switch (mSnakeDirection){
                case GameType.TOP:
                    if(checkCollisionSelf(headerPosition.getX()-1,headerPosition.getY(),true)) {
                        if(checkCollisionSelf(headerPosition.getX()-1,headerPosition.getY()-1,true))
                           setSnakeDirection(GameType.RIGHT);
                        else
                           setSnakeDirection(rand.nextBoolean()?GameType.LEFT:GameType.RIGHT);
                        return;
                    }
                    break;
                case GameType.BOTTOM:
                    if(checkCollisionSelf(headerPosition.getX()+1,headerPosition.getY(),true)) {
                        if(checkCollisionSelf(headerPosition.getX()+1,headerPosition.getY()-1,true))
                           setSnakeDirection(GameType.RIGHT);
                        else
                           setSnakeDirection(rand.nextBoolean()?GameType.LEFT:GameType.RIGHT);
                        return;
                    }
                    break;
                case GameType.LEFT:
                    if(checkCollisionSelf(headerPosition.getX(),headerPosition.getY()-1,true)) {
                        if(checkCollisionSelf(headerPosition.getX()+1,headerPosition.getY()-1,true))
                           setSnakeDirection(GameType.BOTTOM);
                        else
                           setSnakeDirection(rand.nextBoolean()?GameType.TOP:GameType.BOTTOM);
                        return;
                    }
                    break;
                case GameType.RIGHT:
                    if(checkCollisionSelf(headerPosition.getX(),headerPosition.getY()+1,true)) {
                        if(checkCollisionSelf(headerPosition.getX()+1,headerPosition.getY()+1,true))
                           setSnakeDirection(GameType.TOP);
                        else
                           setSnakeDirection(rand.nextBoolean()?GameType.TOP:GameType.BOTTOM);
                        return;
                    }
                    break;
            }

            int distanceX=mSnakeHeader.getX()-mFoodPosition.getX();
            int distanceY=mSnakeHeader.getY()-mFoodPosition.getY();

            //就近决策
            switch (mSnakeDirection){
                case GameType.TOP:
                case GameType.BOTTOM:
                    if(distanceY==0&&Math.abs(distanceX)<4) {
                        timber.log.Timber.d("close to food for Y,dist :"+distanceX);
                        return;
                    }
                    if(distanceX==0&&Math.abs(distanceY)==1){
                        timber.log.Timber.d("food is "+(distanceY>0?"left":"right"));
                        setSnakeDirection(distanceY>0?GameType.LEFT:GameType.RIGHT);
                        return;
                    }
                    break;
                case GameType.LEFT:
                case GameType.RIGHT:
                    if(distanceX==0&&Math.abs(distanceY)<4) {
                        timber.log.Timber.d("close to food for X,dist :"+distanceY);
                        return;
                    }
                    if(distanceY==0&&Math.abs(distanceX)==1){
                        timber.log.Timber.d("food is "+(distanceX>0?"up":"down"));
                        setSnakeDirection(distanceX>0?GameType.TOP:GameType.BOTTOM);
                        return;
                    }
                    break;
            }


            //增加扰动
            goDirection=rand.nextInt(10);
            if(goDirection==2)
                return;
            else if(goDirection==0){
                setSnakeDirection(rand.nextInt(4)+1);
                return;
            }

            //靠近目标，带随机
            goDirection=mSnakeDirection;
            if(mFoodPosition.getX()<mSnakeHeader.getX()){
                if(mFoodPosition.getY()<mSnakeHeader.getY()){
                    if(mSnakeDirection==GameType.TOP)
                        goDirection=rand.nextBoolean()?GameType.LEFT:mSnakeDirection;
                    else
                        goDirection=rand.nextBoolean()?mSnakeDirection:GameType.TOP;
                }else{
                    if(mSnakeDirection==GameType.BOTTOM)
                        goDirection=rand.nextBoolean()?GameType.LEFT:mSnakeDirection;
                    else
                        goDirection=rand.nextBoolean()?mSnakeDirection:GameType.BOTTOM;
                }
            }else{
                if(mFoodPosition.getY()<mSnakeHeader.getY()){
                    if(mSnakeDirection==GameType.TOP)
                        goDirection=rand.nextBoolean()?GameType.RIGHT:mSnakeDirection;
                    else
                        goDirection=rand.nextBoolean()?mSnakeDirection:GameType.TOP;
                }else{
                    if(mSnakeDirection==GameType.BOTTOM)
                        goDirection=rand.nextBoolean()?GameType.RIGHT:mSnakeDirection;
                    else
                        goDirection=rand.nextBoolean()?mSnakeDirection:GameType.BOTTOM;
                }
            }

            setSnakeDirection(goDirection);
        }


        @Override
        public void onDraw(Canvas canvas, Paint mPaint) {
            /*for (int i = 0; i < mGridSize; i++) {
                for (int j = 0; j < mGridSize; j++) {
                    if(mGridSquare.get(i).get(j).mType==GameType.GRID)
                        continue;
                    int left = mStartX + i * mRectSize;
                    int top = mStartY + j * mRectSize;
                    int right = left + mRectSize;
                    int bottom = top + mRectSize;

                    //canvas.drawRect(left, top, right, bottom, mStrokePaint);

                    if(mGridSquare.get(i).get(j).mType==GameType.SNAKE)
                        mGridPaint.setColor(snakeColor);
                    else
                        mGridPaint.setColor(foodColor);
                    canvas.drawRect(left, top, right, bottom, mGridPaint);
                }
            }*/
            if(gridDrawAble)
              onDrawGrid(canvas,mStrokePaint);

            mGridPaint.setColor(foodColor);
            onDrawFood(canvas,mGridPaint);

            mGridPaint.setColor(snakeColor);
            onDrawSnak(canvas,mGridPaint);
        }

        int left,top,right,bottom;

        private void onDrawGrid(Canvas canvas, Paint mPaint){
            for (int i = 0; i < mGridSize; i++) {
                for (int j = 0; j < mGridSize; j++) {
                     left = mStartX + i * mRectSize;
                     top = mStartY + j * mRectSize;
                     right = left + mRectSize;
                     bottom = top + mRectSize;
                    canvas.drawRect(left, top, right, bottom, mStrokePaint);
                }
            }
        }

        private void onDrawSnak(Canvas canvas, Paint mPaint){
            for(GridPosition headerPosition:mSnakePositions){
                left = mStartX + headerPosition.getX() * mRectSize;
                top = mStartY + headerPosition.getY() * mRectSize;
                right = left + mRectSize;
                bottom = top + mRectSize;
                canvas.drawRect(left, top, right, bottom, mPaint);
            }
        }

        private void onDrawFood(Canvas canvas, Paint mPaint){
            left = mStartX + mFoodPosition.getX() * mRectSize;
            top = mStartY + mFoodPosition.getY() * mRectSize;
            right = left + mRectSize;
            bottom = top + mRectSize;
            canvas.drawRect(left, top, right, bottom, mPaint);
        }

        @Override
        public void setAnimatorEntryColor(int color) {
            snakeColor=color;
        }

        //检测碰撞
        private void checkCollision() {
            //检测是否咬到自己
            GridPosition headerPosition = mSnakePositions.get(mSnakePositions.size() - 1);
            if(checkCollisionSelf(headerPosition.getX(),headerPosition.getY(),false)){
                //咬到自己 停止游戏
                mIsEndGame = true;
                return;
            }
        }

        private boolean eatFoodCheck(){
            //判断是否吃到食物
            if (mSnakeHeader.getX() == mFoodPosition.getX()
                    && mSnakeHeader.getY() == mFoodPosition.getY()) {
                mSnakeLength++;
                generateFood();
                return true;
            }
            return false;
        }


        private boolean checkCollisionSelf(int headPositionX,int headPositionY,boolean preface){
            if(mSnakePositions.size()<5)
                return false;
            for (int i = preface?1:0; i < mSnakePositions.size() - 2; i++) {
                GridPosition position = mSnakePositions.get(i);
                if (headPositionX == position.getX() && headPositionY == position.getY()) {
                    //咬到自己
                    return true;
                }
            }
            return false;
        }

        public void reStartGame() {
            if (!mIsEndGame) return;
            for (List<GridSquare> squares : mGridSquare) {
                for (GridSquare square : squares) {
                    square.setType(GameType.GRID);
                }
            }
            if (mSnakeHeader != null) {
                mSnakeHeader.setX(10);
                mSnakeHeader.setY(10);
            } else {
                mSnakeHeader = new GridPosition(10, 10);//蛇的初始位置
            }
            mSnakePositions.clear();
            mSnakePositions.add(new GridPosition(mSnakeHeader.getX(), mSnakeHeader.getY()));
            mSnakeLength = 3;//蛇的长度
            mSnakeDirection = GameType.RIGHT;
            mSpeed = 8;//速度
            if (mFoodPosition != null) {
                mFoodPosition.setX(rand.nextInt(mGridSize));
                mFoodPosition.setY(rand.nextInt(mGridSize));
            } else {
                mFoodPosition = new GridPosition(rand.nextInt(mGridSize), rand.nextInt(mGridSize));
            }
            refreshFood(mFoodPosition);
            mIsEndGame = false;
        }

        //生成food
        private void generateFood() {
            int foodX = rand.nextInt(mGridSize - 1);
            int foodY = rand.nextInt(mGridSize - 1);
            for (int i = 0; i < mSnakePositions.size() - 1; i++) {
                if (foodX == mSnakePositions.get(i).getX() && foodY == mSnakePositions.get(i).getY()) {
                    //不能生成在蛇身上
                    foodX = rand.nextInt(mGridSize - 1);
                    foodY = rand.nextInt(mGridSize - 1);
                    //重新循环
                    i = 0;
                }
            }
            mFoodPosition.setX(foodX);
            mFoodPosition.setY(foodY);
            refreshFood(mFoodPosition);
        }

        private void moveSnake(int snakeDirection) {
            switch (snakeDirection) {
                case GameType.LEFT:
                    if (mSnakeHeader.getX() - 1 < 0) {//边界判断：如果到了最左边 让他穿过屏幕到最右边
                        mSnakeHeader.setX(mGridSize - 1);
                    } else {
                        mSnakeHeader.setX(mSnakeHeader.getX() - 1);
                    }
                    mSnakePositions.add(new GridPosition(mSnakeHeader.getX(), mSnakeHeader.getY()));
                    break;
                case GameType.TOP:
                    if (mSnakeHeader.getY() - 1 < 0) {
                        mSnakeHeader.setY(mGridSize - 1);
                    } else {
                        mSnakeHeader.setY(mSnakeHeader.getY() - 1);
                    }
                    mSnakePositions.add(new GridPosition(mSnakeHeader.getX(), mSnakeHeader.getY()));
                    break;
                case GameType.RIGHT:
                    if (mSnakeHeader.getX() + 1 >= mGridSize) {
                        mSnakeHeader.setX(0);
                    } else {
                        mSnakeHeader.setX(mSnakeHeader.getX() + 1);
                    }
                    mSnakePositions.add(new GridPosition(mSnakeHeader.getX(), mSnakeHeader.getY()));
                    break;
                case GameType.BOTTOM:
                    if (mSnakeHeader.getY() + 1 >= mGridSize) {
                        mSnakeHeader.setY(0);
                    } else {
                        mSnakeHeader.setY(mSnakeHeader.getY() + 1);
                    }
                    mSnakePositions.add(new GridPosition(mSnakeHeader.getX(), mSnakeHeader.getY()));
                    break;
            }
        }

        private void refreshGridSquare() {
            for (GridPosition position : mSnakePositions) {
                mGridSquare.get(position.getX()).get(position.getY()).setType(GameType.SNAKE);
            }
        }

        private void handleSnakeTail() {
            int snakeLength = mSnakeLength;
            for (int i = mSnakePositions.size() - 1; i >= 0; i--) {
                if (snakeLength > 0) {
                    snakeLength--;
                } else {//将超过长度的格子 置为 GameType.GRID
                    GridPosition position = mSnakePositions.get(i);
                    mGridSquare.get(position.getX()).get(position.getY()).setType(GameType.GRID);
                }
            }
            snakeLength = mSnakeLength;
            for (int i = mSnakePositions.size() - 1; i >= 0; i--) {
                if (snakeLength > 0) {
                    snakeLength--;
                } else {
                    mSnakePositions.remove(i);
                }
            }
        }

        public boolean isGridDrawAble() {
            return gridDrawAble;
        }

        public void setGridDrawAble(boolean gridDrawAble) {
            this.gridDrawAble = gridDrawAble;
        }
    }


    public class GridSquare {
        private int mType;//元素类型

        public GridSquare(int type) {
            mType = type;
        }

        public int getColor() {
            switch (mType) {
                case GameType.GRID://空格子
                    return Color.WHITE;
                case GameType.FOOD://食物
                    return Color.BLUE;
                case GameType.SNAKE://蛇
                    return Color.parseColor("#FF4081");
            }
            return Color.WHITE;
        }

        public void setType(int type) {
            mType = type;
        }
    }

    public class GridPosition {
        private int x;
        private int y;

        public GridPosition(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }
    }

    public interface GameType {
        int GRID = 0;
        int FOOD = 1;
        int SNAKE = 2;

        int LEFT = 1;
        int TOP = 2;
        int RIGHT = 3;
        int BOTTOM = 4;
    }
}
